#include<stdio.h>
#include<omp.h>
#include<stdlib.h>
#include <string.h>
#include<ctype.h>

/*Define of our global variables*/
#define NTHREADS 8
#define P_SIZE 500
#define W_SIZE 30

/*
 * Function: inEndOfPhrase
 * Param: char c
 * return int: 1 - TRUE; 0 - FALSE
 * Verify if the char indicates an end of phrase
*/
int isEndOfPhrase(char c);

/*
 * Encontra as posicoes do vetor com o texto dentro das quais
 * cada thread vai atuar
 * @ ENTRADA
 *   *text - vetor de caracter com o texto analisado
 *   fileSize - tamanho do vetor de texto
 *   nThreads - numero de threads
 * @ SAIDA
 *   *cutoffs - vetor de inteiros com as posicoes que delimitam
 *              cada bloco
 */
void findCutoffs(int *cutoffs, char *text, long int fileSize, int nThreads);

/*
 * Function: ClearString
 * Params: char *String
 * return: VOID
 * Clean the String
 */
void ClearString(char *String);

/*
 * Function: W_isPalindrome
 * Params: char *String
 * return: int: 1 - TRUE; 0 - FALSE
 * Verify if the String received is a palindrome.
 * There's only one difference between this function and the P_isPalindrome:
 * Here we use an array of W_SIZE = 30 and the other uses P_SIZE = 500
 */
int W_isPalindrome(char *String);

/*
 * Function: P_isPalindrome
 * Params: char *String
 * return: int: 1 - TRUE; 0 - FALSE
 * Verify if the String received is a palindrome.
 * There's only one difference between this function and the W_isPalindrome:
 * Here we use an array of W_SIZE = 500 and the other uses W_SIZE = 30
 */
int P_isPalindrome(char *String);

/*
 * Function: inEndOfWord
 * Param: char c
 * return int: 1 _ TRUE; 0 - FALSE
 * Verify if the char indicates an end of word
*/
int isEndOfWord(char c);

/*
 * Function: inTheWord
 * Param: char c
 * return int: 1 _ TRUE; 0 - FALSE
 * Verify if the char is in the word
*/
int inTheWord(char c);

/*
 * Function: CountPalindrome
 * Param: char *StringTemp: vetor com o texto a ser analisado
 *        long int sizeFile: tamanho do vetor StringTemp
 *        int numberOfPalindromes[]: One array with the number of palindromes found
                      [0] Number of Palindrome words
                      [1] Number of Palindrome phrases
 * Verify if each word and sentence from the file is a palindrome.
*/
void CountPalindrome(char *StringTemp, long int sizeFile, int numberOfPalindromes[]);

int main(void){

  int        nPalindW = 0, nPalindP = 0;
  long int   fileSize;
  FILE       *f = fopen("shakespe.txt", "r");
  char       *text;
  int        cutoffs[NTHREADS + 1];


  if(f == NULL) {
    printf("Nao foi possivel abrir o arquivo\n");
    exit(1);
  }
  /*
   * calcula o tamanho do arquivo de texto
   */
  fseek(f, 0, SEEK_END);
  fileSize = ftell(f);
  /* aloca um vetor de char para armazenar todo o arquivo
   * em memoria principal
   */
  text = (char*) malloc(fileSize + 1);
  if(text == NULL) {
    printf("Erro ao tentar alocar memoria para o texto\n");
    exit(1);
  }

  /*
   * retorna o cursor para o inicio do arquivo
   */
  rewind(f);
  /*
   * salva todo o arquivo no vetor 'block'
   */
  fread(text, sizeof(char), fileSize + 1, f);
  /*
   * encontra as posicoes do vetor 'block' dentro das quais cada
   * thread vai computar
   */
  findCutoffs(cutoffs , text, fileSize, NTHREADS);

#pragma omp parallel num_threads(NTHREADS) firstprivate(text, cutoffs) reduction(+:nPalindW) reduction(+:nPalindP)
  {
    int begin, end;
    int n[2];
    int threadNumber;

    threadNumber = omp_get_thread_num(); /* obtem o nro da thread */
    begin = cutoffs[threadNumber]; /* obtem o inicio do bloco a ser analisado */
    end = cutoffs[threadNumber + 1]; /* obtem o fim do bloco a ser analisado */

    /*
     * calcula o nro de palindromos
     */
    CountPalindrome(&text[begin], end - begin, n);
    printf("\n ------------------\n Thread %d\n ------------------\n Palindromes found:\n Word - %7d\n Phrases - %4d\n", threadNumber,  n[0], n[1]);
    nPalindW = n[0];
    nPalindP = n[1];
  }

  printf("\n ---------------------------\n");
    printf(" Total of Palindromes found:\n Words: %7d\n Phrases: %5d\n\n", nPalindW, nPalindP);

  return 0;
}

int isEndOfPhrase(char c){
    //if the char is one of this symbols, its an end of phrase
    if(c == '.' || c == ';' || c == '(' || c == ')' || c == '"' ||
       c == ':' || c == '<' || c == '>' || c == '{' || c == '}' ||
       c == '[' || c == ']' || c == '|' || c == '&' || c == '!' ||
       c == '?' || c == '\n'|| c == '\t'|| c == '\r' )
       return 1;
    else return 0;
}

void findCutoffs(int *cutoffs, char *text, long int fileSize, int nThreads){
  int blockSize;
  int i;

  /*
   * calcula o tamanho aproximado que os blocos de caracteres que cada tread
   * vai computar devem ter
   */
  blockSize = fileSize / nThreads;
  cutoffs[0] = 0; /* o inicio do 1o bloco eh sempre zero */
  /*
   * encontra, no texto, o proximo final de frase a partir
   * de cada ponto que deveria marcar o fim de bloco
   */
  for(i = 1; i < nThreads; i++) {
    /* recebe a posicao que deveria marcar o fim do i-esimo bloco */
    cutoffs[i] = i * blockSize;
    while( !isEndOfPhrase(text[ cutoffs[i] ]) )
      cutoffs[i]++;
    cutoffs[i]++;
  }
  /* o final do ultimo bloco sempre eh o fim do texto */
  cutoffs[i] = fileSize;
}

void ClearString(char *String){
    //Gets the size of the string and change every char for a '\0'
    int i, size = strlen(String);
    for(i=0; i< size; i++){
        String[i] = '\0';
    }
}

int W_isPalindrome(char *String){
    // We create the local variables and set the initial values
    char inverse_String[W_SIZE];
    int count, size = strlen(String);
    int i;
    count = size-1;
    ClearString(inverse_String);

    // If the String isn't just a letter, i.e: 'a', verify if its a palindrome
    if(strlen(String) > 1){
        //transform all the letters to lower chars
        for(i=0; i<size; i++){
            String[i] = tolower(String[i]);
        }

        //copy the string to a aux string in the inverse
        for(i=0; i<size; i++){
            inverse_String[i] = String[count];
            count--;
        }
        //compare both strings
        if(strcmp(inverse_String, String) == 0){
            return 1;
        }
        else{
            return 0;
        }
    }
    else return 0;
}

int P_isPalindrome(char *String){
    // We create the local variables and set the initial values
    char inverse_String[P_SIZE];
    int count, size = strlen(String);
    int i;

    count = size-1;
    ClearString(inverse_String);

    // If the String isn't just a letter, i.e: 'a', verify if its a palindrome
    if(strlen(String) > 1){
        //transform all the letters to lower chars
        for(i=0; i<size; i++){
            String[i] = tolower(String[i]);
        }

        //copy the string to a aux string in the inverse
        for(i=0; i<size; i++){
            inverse_String[i] = String[count];
            count--;
        }

        //compare both strings
        if(!strcmp(inverse_String, String)){
            return 1;
        }
        else{
            return 0;
        }
    }
    else return 0;
}

int isEndOfWord(char c){
    //if the char is one of this symbols, its just an end of word
    if(c == ' ' || c == ',' || c == '-' || c == '_'  || c == 39)
        return 1;
    else return 0;
}

int inTheWord(char c){
    //if the char is one of this symbols, its not the final of the word
    if(((c >= 'A') && (c <= 'Z')) || ((c >= 'a') && (c <= 'z')) || ((c >= '0') && (c <= '9')))
        return 1;
    else return 0;

}

void CountPalindrome(char *StringTemp, long int sizeFile, int numberOfPalindromes[]){
    // Create and Set the variables and its initials values
    char Word[W_SIZE];
    char Phrase[P_SIZE];
    char c;
    int counter=0;
    long int i;


    numberOfPalindromes[0] = 0;
    numberOfPalindromes[1] = 0;

    ClearString(Word);

    // For each char of the file, analyses if indicates an end of word, phrase or is in the middle of word
    for(i=0; i<sizeFile; i++){
        c = StringTemp[i];
        //If it is a letter or number, continue the analysis
        if( inTheWord(c) ){
            Word[counter] = c;
            counter++;
        }
        //If it is just an end of word, verify if is a palindrome
        else if( isEndOfWord(c) ){
            strcat(Phrase, Word);
            if(W_isPalindrome(Word) && strlen(Word) > 1){
                numberOfPalindromes[0]++;
            }

            ClearString(Word);
            counter=0;
        }
        //If is an end of Phrase, verify if the last word is a palindrome and then verify if the phrase is a palindrome
        else if( isEndOfPhrase(c) ){

            strcat(Phrase, Word);

            if(W_isPalindrome(Word) && strlen(Word) > 1){
                numberOfPalindromes[0]++;
            }

            if(P_isPalindrome(Phrase) && strlen(Phrase) > 1){
                numberOfPalindromes[1]++;
            }
            ClearString(Word);
            ClearString(Phrase);
            counter = 0;
        }
    }
}
